
#include <BFont>
#include <BPainter>
#include <BImage>
#include <BKeyEvent>
#include <BChangeEvent>
#include <BMouseEvent>
#include <BCharEvent>
#include <BFocusEvent>
#include <BScrollBar>
#include <BSystem>

#include <BTextEdit>
#include "member_BTextEdit.h"
#include "member_BWidget.h"
#include "member_BFont.h"

using namespace BWE;

#define member					(*(member_BTextEdit*)_ptr)
#define member_allocate()		_ptr = new member_BTextEdit(this)
#define member_release()		delete (member_BTextEdit*)_ptr

BTextEdit::BTextEdit(BWidget* parent) : BScrollArea(parent)
{
	member_allocate();
}
BTextEdit::~BTextEdit()
{
	member_release();
}

bool BTextEdit::setText(const BString& text)
{
	member.text = text;
	member.initBlocks();
	this->fresh();
	return true;
}
bool BTextEdit::addText(const BString& text)
{
	member.appendBlocks(text);
	this->fresh();
	return true;
}
const BString& BTextEdit::text() const
{
	if (member.changed)
	{
		member.text.reset();
		for (int i = 0; i < member.blocks.size(); i++)
		{
			TextBlock& block = member.blocks[i];
			for (int bi = 0; bi < block.cells.size(); bi++)
			{
				member.text.append(block.cells[bi].code);
			}
		}
		member.changed = false;
	}
	return member.text;
}

void BTextEdit::setLineShown(bool lineShown)
{
	if (member.lineShown != lineShown)
	{
		member.lineShown = lineShown;
		member.updateLineNumbers();
	}
}
bool BTextEdit::lineShown() const
{
	return member.lineShown;
}

void BTextEdit::clear()
{
	if (member.blocks.size())
	{
		member.text.clear();
		member.blocks.clear();
		member.changed = false;
		member.showFirst.reset();
		member.showLast.reset();
		this->fresh();
	}
}

bool BTextEdit::changed() const
{
	return member.changed;
}

void BTextEdit::setLineWrap(bool lineWrap)
{
	if (member.lineWrap != lineWrap)
	{
		member.lineWrap = lineWrap;
		for (int i = 0; i < member.blocks.size(); i++)
		{
			member.blocks[i].dirty = true;
		}
		this->fresh();
	}
}
bool BTextEdit::lineWrap() const
{
	return member.lineWrap;
}

void BTextEdit::setBlinkTime(int ms)
{
	if (member.blinkTime != ms)
	{
		member.blinkTime = ms;
		this->fresh();
	}
}
int BTextEdit::blinkTime() const
{
	return member.blinkTime;
}

void BTextEdit::setReadOnly(bool readOnly)
{
	if (member.readOnly != readOnly)
	{
		member.readOnly = readOnly;
		member.inputBlock.reset();
		member.inputIndex = 0;
		this->fresh();
	}
}
bool BTextEdit::readOnly() const
{
	return member.readOnly;
}

void BTextEdit::scrollArea()
{
	this->fresh();
}

void BTextEdit::updateEvent(const BEvent& event)
{
	if (this->focus() && !member.readOnly)
	{
		bool inputShown = member.inputShown;
		if (member.blinkTime > 0)
		{
			clock_t now = clock();
			if (now - member.blinkTick > member.blinkTime)
			{
				member.blinkTick = now;
				inputShown = !member.inputShown;
			}
		}
		else
		{
			inputShown = true;
		}
		if (member.inputShown != inputShown)
		{
			member.inputShown = inputShown;
			this->fresh();
		}
	}
}
void BTextEdit::changeEvent(const BChangeEvent& changeEvent)
{
	if (changeEvent.change() == Change_Style)
	{
		const BFont* font = this->font();
		if (font)
		{
			int fontHeight = font->height();
			this->verticalScrollBar()->setStep(fontHeight);
			for (int i = 0; i < member.blocks.size(); i++)
			{
				member.blocks[i].refresh((BFont*)font);
				member.blocks[i].dirty = true;
			}
			member.updateLineNumbers();
			this->fresh();
		}
	}
}

void BTextEdit::focusEvent(const BFocusEvent& focusEvent)
{
	if (focusEvent.type() == Focus_Out)
	{
		member.inputShown = false;
	}
}
void BTextEdit::keyDownEvent(const BKeyEvent& keyEvent)
{
	if (BSystem::ckeyState(Key_LCtrl) < 0)
	{
		if (keyEvent.key() == Key_V)
		{
			if (member.selected)
			{
				member.remove();
			}
			BString text;
			bool ret = BSystem::GetClipBoardText(text);
			member.input(text);
		}
		if (keyEvent.key() == Key_C)
		{
			BString text;
			bool ret = BSystem::SetClipBoardText(text);
		}
	}
	if (keyEvent.key() == Key_Left)
	{
		if (BSystem::ckeyState(Key_LCtrl) < 0)
		{
			member.seekPrevWord();
		}
		else if (member.selected)
		{
			if (member.beginBlock.valid())
				member.inputBlock = member.beginBlock;
			member.inputIndex = member.beginIndex;
			member.unselected();
		}
		else
		{
			member.seekPrev();
		}
	}
	if (keyEvent.key() == Key_Right)
	{
		if (BSystem::ckeyState(Key_LCtrl) < 0)
		{
			member.seekNextWord();
		}
		else if (member.selected)
		{
			if (member.endBlock.valid())
				member.inputBlock = member.endBlock;
			member.inputIndex = member.endIndex;
			member.unselected();
		}
		else
		{
			member.seekNext();
		}
	}
	if (keyEvent.key() == Key_Up)
	{
		if (member.inputBlock->line > 0)
		{
			bool inTail = member.inputIndex == member.inputBlock->tail();
			member.inputBlock -= 1;
			if (inTail)
				member.inputIndex = member.inputBlock->tail();
			member.inputIndex = bLimit(member.inputIndex, 0, member.inputBlock->tail());
		}
	}
	if (keyEvent.key() == Key_Down)
	{
		if (member.inputBlock->line < member.blocks.size() - 1)
		{
			bool inTail = member.inputIndex == member.inputBlock->tail();
			member.inputBlock += 1;
			if (inTail)
				member.inputIndex = member.inputBlock->tail();
			member.inputIndex = bLimit(member.inputIndex, 0, member.inputBlock->tail());
		}
	}
	if (keyEvent.key() == Key_Delete)
	{
		member.remove();
	}
	if (keyEvent.key() == Key_Back)
	{
		if (!member.selected)
			member.seekPrev();
		member.remove();
	}
	member.inputShown = true;
	member.blinkTick = clock() + 500;
	this->fresh();
}
void BTextEdit::keyUpEvent(const BKeyEvent& keyEvent)
{

}
void BTextEdit::charEvent(const BCharEvent& charEvent)
{
	BCode code = charEvent.code();
	if (code == 13)
		code = '\n';
	if (code < 32 && code != '\t' && code != '\n')
		return;
	if (member.selected)
		member.remove();
	member.input(code);
	this->fresh();
}

void BTextEdit::freshEvent(const BEvent& event)
{
	BScrollArea::freshEvent(event);
	member.adjustBlocks();
}
void BTextEdit::resizeEvent(const BEvent& event)
{
	BScrollArea::resizeEvent(event);
	for (int i = 0; i < member.blocks.size(); i++)
	{
		member.blocks[i].dirty = true;
	}
}
void BTextEdit::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();
	painter.drawClient();

	if (member.selected)
		member.paintSelection(painter);

	painter.setColor(Color_Text);
	for (auto it = member.showFirst; it < member.showLast; it++)
	{
		BPoint offset = it->rect.pos();
		for (int ci = 0; ci < it->cells.size(); ci++)
		{
			const CodeCell& cell = it->cells[ci];
			if (cell.image)
			{
				painter.drawImage(cell.image_rect + offset, cell.image);
			}
		}
	}

	if (member.inputShown && member.inputBlock.valid())
		member.paintCursor(painter);

	painter.drawPerch();
	if (member.lineShown)
	{
		painter.setColor(0, 89, 120);
		const BRect& leftRect = this->rect(Part_Left);
			int x = leftRect.x();
			int w = leftRect.width();
		for (auto it = member.showFirst; it < member.showLast; it++)
		{
			int y = it->rect.y();
			int h = it->rect.height();
			painter.drawText(x, y, w, h, it->lineText);
		}
	}

	if (member.inputBlock.valid())
	{
		static BString text;
		text.reset();
		text << "input : " << member.inputBlock->line << '(' << member.inputIndex << ')';
		painter.drawText(this->rect(Part_Top), text);
	}

	painter.drawBorder();
	painter.drawMargin();
}

void BTextEdit::mouseEnterEvent(const BMouseEvent& mouseEvent)
{

}
void BTextEdit::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{

}

void BTextEdit::mousePressEvent(const BMouseEvent& mouseEvent)
{
	BPoint pos = mouseEvent.pos();
	if (mouseEvent.button() == Button_Left)
	{
		member.updateHover(pos);
		BRect leftRect = this->rect(Part_Left);
		if (leftRect.contain(pos))
		{
			member.inputBlock = member.hoverBlock;
			member.inputIndex = 0;
			member.beginIndex = 0;
			member.endIndex = member.inputBlock->cells.size();
			member.pressed = true;
			member.pressPoint = pos;
		}
		else if (BSystem::ckeyState(Key_LCtrl) < 0)
		{
			member.inputBlock = member.hoverBlock;
			member.inputIndex = member.hoverIndex;
			member.selectWord();
		}
		else if(BSystem::ckeyState(Key_LShift) < 0)
		{
			member.pressIndex = member.inputIndex;
			member.selectBlocks();
		}
		else
		{
			member.selected = false;
			member.pressed = true;
			member.pressIndex = member.hoverIndex;
			member.pressPoint = pos;

			member.inputBlock = member.hoverBlock;
			member.inputIndex = member.hoverIndex;
			if (member.inputBlock->cells.last().code == '\n' && member.inputIndex == member.inputBlock->cells.size())
				member.inputIndex--;

			member.beginBlock.reset();
			member.beginIndex = 0;
			member.endBlock.reset();
			member.endIndex = 0;

			member.inputShown = true;
			member.blinkTick = clock() + 500;
		}
		this->fresh();
	}
}
void BTextEdit::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (member.pressed)
		{
			member.pressed = false;
			member.pressIndex = 0;
		}
	}
}
void BTextEdit::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	BPoint pos = mouseEvent.pos();

	if (member.pressed)
	{
		const BRect& crect = this->clientRect();
		if (pos.x() < crect.x())
			pos.x() = crect.x();
		member.updateHover(pos);
		member.selectBlocks();
		this->fresh();
	}
}
void BTextEdit::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	member.updateHover(mouseEvent.pos());
	member.selectWord();
	this->fresh();
}
